#include "lua.h"
#include "lauxlib.h"
#include "lualib.h"

#include <assert.h>
#include <time.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

#include "../dlist.h"

static int xl_func_CallDelay(lua_State * L);

#define MAKE_LUA_FUNCTION(x)  {#x, xl_func_##x}
static const struct luaL_reg x_lib[] = {
    MAKE_LUA_FUNCTION(CallDelay),
    {NULL, NULL}
};

typedef struct Call{
    struct Call* prev;
    struct Call* next;
    char name[256];
} Call;

static int func_id = 0;
static Call * call_list = 0;

static void LinkToCallBack(Call* c)  
{
    dlist_insert_tail(call_list, c);
}

const char  *SetCallBackTable(lua_State * L, int func_pos)
{
    assert(lua_type(L, func_pos) == LUA_TFUNCTION);
    int __top = lua_gettop(L);

    int iParamNum = lua_gettop(L) - func_pos;

    lua_getglobal(L, "___lua_callback_table");
    if ( lua_isnil(L, -1) ) {
        lua_pop(L, 1);
        lua_newtable(L);
        lua_setglobal(L, "___lua_callback_table");
        lua_getglobal(L, "___lua_callback_table");
    }

    static char name[256] = {0};
    sprintf(name, "___lua_callback_%u", func_id++);

    lua_pushstring(L, name);     // key of callback event
    lua_newtable(L);                // table for callback event
    {
        // set call back func
        lua_pushstring(L, "___func");
        lua_pushvalue(L, 1);
        lua_settable(L, -3);

        // set param count
        lua_pushstring(L, "___param");
        lua_pushinteger(L, iParamNum);
        lua_settable(L, -3);

        // push param
        int i;
        for (i = 0; i < iParamNum; i++) {
            lua_pushinteger(L, i+1);
            lua_pushvalue(L, i+2);
            lua_settable(L, -3);
        }
    }
    lua_settable(L, -3); // push call back table to global table;

    // pop global table
    lua_pop(L, 1);

    assert(lua_gettop(L) == __top);
    return name;
}

void RemoveLuaCallBack(lua_State * L, const char * name)
{
    lua_getglobal(L, "___lua_callback_table");
    if (!lua_istable(L, -1)) {
        lua_pop(L, 1);
        return;
    }
    lua_pushnil(L);
    lua_setfield(L, -2, name);
    lua_pop(L, 1);
}

int CallLuaCallBack(lua_State * L, const char * name)
{
    int ret = -1;
    lua_getglobal(L, "___lua_callback_table");
    if (!lua_istable(L, -1)) {
        lua_pop(L, 1);
        return -1;
    }

    do {  // ___lua_callback_table
        lua_getfield(L, -1, name);
        if (!lua_istable(L, -1)) {
            lua_pop(L, 1);
            break;
        }
        do { // in call back table
            lua_getfield(L, -1, "___param");
            int iParam = lua_tointeger(L, -1);
            lua_pop(L, 1);

            lua_getfield(L, -1, "___func");
            int i;
            for (i = 0; i < iParam; i++) {
                lua_pushinteger(L, i+1);
                lua_gettable(L, - i - 3);
            }
            lua_pcall(L, iParam, 0, 0);
            ret = 0;
        } while(0); // in call back table
        lua_pop(L, 1);
    } while(0); // ___lua_callback_table
    lua_pop(L, 1);
    return ret;
}

int xl_func_CallDelay(lua_State * L)
{
    Call * c = malloc(sizeof(Call));
    c->next = 0;
    sprintf(c->name, "%s", SetCallBackTable(L, 1));
    LinkToCallBack(c);
    return 0;
}

int main(int argc, char * argv[])
{
    lua_State * L = luaL_newstate();
    luaL_openlibs(L);
    luaL_openlib(L, "x",  x_lib,  0);

    int i;
    for (i = 1; i < argc; i++) {
        if (luaL_dofile(L, argv[i]) != 0) {
            printf("dofile %s failed\n", argv[i]);
            return -1;
        }
    }

    while (call_list) {
        Call * c = call_list;
        CallLuaCallBack(L, c->name);
        RemoveLuaCallBack(L, c->name);
        dlist_remove(call_list, c);
        free(c);
    }
    lua_close(L);
    return 0;
}
